package com.easyaop.asm;

import com.easyaop.api.Context;
import com.easyaop.api.LoggerFactory;
import com.easyaop.api.advisor.Advisor;
import com.easyaop.api.advisor.AroundAdvisor;
import com.easyaop.api.advisor.EventAdvisor;
import com.easyaop.api.around.Around;
import com.easyaop.api.event.listener.MethodListener;
import com.easyaop.api.pointcut.ClassObject;
import com.easyaop.api.pointcut.Pointcut;
import com.easyaop.asm.bytecode.ByteUtils;
import com.easyaop.asm.bytecode.DefaultQuery;
import com.easyaop.asm.pointcut.ForAsm;
import com.easyaop.commons.utils.PointcutUtils;
import com.easyaop.core.AbstractWeaver;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class AsmTransformer implements ClassFileTransformer {
    private static final Logger LOGGER = LoggerFactory.getLogger("Transformer");
    static boolean isSynthetic(int mod) {
        return (mod & 0x00001000) != 0;
    }

    @Override
    public byte[] transform(ClassLoader loader, String name, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException {
        try {

            String className = name.replace('/', '.');
//            if (!className.contains("MyBean2")) {
//                return classfileBuffer;
//            }
            if (AbstractWeaver.skip(className)) {
                return classfileBuffer;
            }

            Set<Advisor> advisors = Context.ADVISORS;

            List<Advisor> filterAdvisor = advisors.stream()
                    .filter(advisor -> advisor.getPointcut().isClass(new ClassObject.ForUnloaded(className)))
                    .collect(Collectors.toList());

            if(filterAdvisor.isEmpty()){
                return classfileBuffer;
            }

            byte[] newBytes = classfileBuffer;
            ByteUtils byteUtils = new ByteUtils();

            ClassNode classNode = new ClassNode();
            new ClassReader(classfileBuffer).accept(classNode, ClassReader.SKIP_CODE);

//            classNode.
            for (MethodNode methodNode : classNode.methods) {

                int access = methodNode.access;
                if(Modifier.isAbstract(access)
                        || Modifier.isNative(access)
//                        || isSynthetic(access)
//                        || "<init>".equals(methodNode.name)
//                        || "<clinit>".equals(methodNode.name)
//                        || "valueOf".equals(methodNode.name)
                ){
                    continue;
                }

                boolean isEvent = false;
                String methodName = methodNode.name;
                String desc = methodNode.desc;

                for (Advisor advisor : filterAdvisor) {
                    Pointcut pointcut = advisor.getPointcut();
                    if (!pointcut.isMethod(new ForAsm(classNode.name, methodNode))) {
                        continue; // 匹配失败
                    }
                    advisor.addSignature(PointcutUtils.getSignature(className, methodName, desc));
                    if (advisor instanceof AroundAdvisor) {
                        AroundAdvisor aroundAdvisor = (AroundAdvisor) advisor;
                        for (Around around : aroundAdvisor) {
                            newBytes = byteUtils.around(new DefaultQuery(loader, className, newBytes, methodName, desc, around.getClass()));
                        }
                    } else if (advisor instanceof EventAdvisor) {
                        isEvent = true;
                    }

                }
                if (isEvent) {
                    newBytes = byteUtils.around(new DefaultQuery(loader, className, newBytes, methodName, desc, Around2Event.class));
                }
            }

            if (classfileBuffer == newBytes) {
                return classfileBuffer;
            }
            LOGGER.info("weaver " + className + " - ok");
            return newBytes;
        } catch (Throwable e) {
            LOGGER.log(Level.WARNING
                    , "weaver error " + name + " - ", e);
            throw new RuntimeException(e);
        }
    }

}
